/*
 * Copyright (c) 2023-2023 elsfs Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.elsfs.cloud.common.sms.service;

import com.aliyun.auth.credentials.Credential;
import com.aliyun.auth.credentials.provider.ICredentialProvider;
import com.aliyun.auth.credentials.provider.StaticCredentialProvider;
import com.aliyun.sdk.service.dysmsapi20170525.AsyncClient;
import com.aliyun.sdk.service.dysmsapi20170525.models.SendBatchSmsRequest;
import com.aliyun.sdk.service.dysmsapi20170525.models.SendBatchSmsResponse;
import com.aliyun.sdk.service.dysmsapi20170525.models.SendSmsRequest;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import darabonba.core.client.ClientOverrideConfiguration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import lombok.RequiredArgsConstructor;
import org.elsfs.cloud.common.sms.entity.SendSmsResponse;
import org.elsfs.cloud.common.sms.properties.SmsProperties;

/**
 * 阿里云短信发送
 *
 * @author zeng
 */
@RequiredArgsConstructor
public class AliyunSmsService implements ISmsService {
  private final SmsProperties smsProperties;
  private final ObjectMapper objectMapper;
  private static final Gson GSON = new Gson();

  @Override
  public SendSmsResponse SendSms(
      String phone, String templateCode, Map<String, Object> templateParam) {
    SmsProperties.Aliyun aliyun = smsProperties.getAliyuns().get(templateCode);
    if (aliyun == null) {
      throw new RuntimeException("短信配置配置错误");
    }
    ICredentialProvider provider =
        credentialProvider(aliyun.getAccessKeyId(), aliyun.getAccessKeySecret());

    try (AsyncClient asyncClient = asyncClient(provider)) {
      SendSmsRequest sendSmsRequest =
          sendSmsRequest(phone, aliyun.getSignName(), templateCode, templateParam);
      CompletableFuture<com.aliyun.sdk.service.dysmsapi20170525.models.SendSmsResponse> response =
          asyncClient.sendSms(sendSmsRequest);
      com.aliyun.sdk.service.dysmsapi20170525.models.SendSmsResponse resp = response.get();
      String json = GSON.toJson(resp);
      return objectMapper.readValue(json, SendSmsResponse.class);
    } catch (JsonProcessingException | InterruptedException | ExecutionException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public SendSmsResponse sendBatchSms(
      List<String> phoneNumbers, String templateCode, List<Map<String, Object>> templateParam) {
    SmsProperties.Aliyun aliyun = smsProperties.getAliyuns().get(templateCode);

    ICredentialProvider provider =
        credentialProvider(aliyun.getAccessKeyId(), aliyun.getAccessKeySecret());
    try (AsyncClient asyncClient = asyncClient(provider)) {
      SendBatchSmsRequest smsRequest =
          SendBatchSmsRequest.builder()
              .templateCode(templateCode)
              .phoneNumberJson(objectMapper.writeValueAsString(phoneNumbers))
              .signNameJson(aliyun.getSignName())
              .templateCode(templateCode)
              .templateParamJson(objectMapper.writeValueAsString(templateParam))
              .build();
      SendBatchSmsResponse response = asyncClient.sendBatchSms(smsRequest).get();
      String json = GSON.toJson(response);
      return objectMapper.readValue(json, SendSmsResponse.class);
    } catch (JsonProcessingException | ExecutionException | InterruptedException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * 构建并返回一个发送短信的请求对象。
   *
   * @param phone 目标手机号码。
   * @param signName 短信签名。
   * @param templateCode 短信模板代码。
   * @param templateParam 短信模板参数，以键值对形式提供。
   * @return SendSmsRequest 短信发送请求对象，包含了手机号、签名、模板代码和模板参数。
   * @throws JsonProcessingException 如果将模板参数转换为JSON字符串时发生错误。
   */
  protected final SendSmsRequest sendSmsRequest(
      String phone, String signName, String templateCode, Map<String, Object> templateParam)
      throws JsonProcessingException {
    // 将模板参数转换为JSON字符串
    String templateParamString = objectMapper.writeValueAsString(templateParam);
    // 构建发送短信的请求对象
    return SendSmsRequest.builder()
        .phoneNumbers(phone)
        .signName(signName)
        .templateCode(templateCode)
        .templateParam(templateParamString)
        // 此处可以进行请求级别的配置重写，比如设置HTTP请求参数等
        // .requestConfiguration(RequestConfiguration.create().setHttpHeaders(new
        // HttpHeaders()))
        .build();
  }

  /**
   * 创建一个异步客户端实例。 此方法配置了认证提供者、服务端点以及一些客户端级别的配置重写，用于发送异步请求。
   *
   * @param provider 认证信息提供者，用于提供访问阿里云服务所需的认证信息。
   * @return 配置好的异步客户端实例，可用于发送异步请求。
   */
  protected final AsyncClient asyncClient(ICredentialProvider provider) {
    return AsyncClient.builder()
        // 配置认证提供者，这是设置访问阿里云服务所需认证信息的地方。
        .credentialsProvider(provider)
        // 配置重写，可用于设置客户端级别的配置，例如端点、HTTP请求参数等。
        .overrideConfiguration(
            ClientOverrideConfiguration.create()
                // 设置端点为阿里云短信服务API的地址。
                .setEndpointOverride("dysmsapi.aliyuncs.com")
            // 这里可以添加更多配置，例如连接超时时间等。
            )
        .build();
  }

  /**
   * 创建一个凭证提供器，用于提供阿里云API访问所需的凭证。
   *
   * @param accessKey 访问密钥ID，是阿里云账号或RAM用户的身份标识。
   * @param accessKeySecret 访问密钥秘钥，与访问密钥ID配对使用，用于验证身份。
   * @return 返回一个ICredentialProvider接口实例，该实例包含设置好的访问凭证。
   */
  private ICredentialProvider credentialProvider(String accessKey, String accessKeySecret) {
    // 使用提供的accessKey和accessKeySecret创建静态凭证提供器
    return StaticCredentialProvider.create(
        Credential.builder()
            // 设置访问密钥ID和访问密钥秘钥
            .accessKeyId(accessKey)
            .accessKeySecret(accessKeySecret)
            // 安全令牌的设置，可用于使用STS临时凭证的情况，此处未启用
            // .securityToken(System.getenv("ALIBABA_CLOUD_SECURITY_TOKEN")) // 使用STS令牌
            .build());
  }
}
